/*
 * Copyright the original author or authors.
 * 
 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.kairos.engine 
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.getTimer;
	
	import com.bourre.log.PixlibStringifier;
	import com.bourre.transitions.FPSBeacon;
	import com.bourre.transitions.TickBeacon;
	import com.bourre.transitions.TickListener; 

	/**
	 * <code>RTBeacon</code> is the global Kairos animation metronome. Objects
	 * plug themselves onto the beacon and receive an <code>onTick</code> event
	 * at each step of computation.
	 * <p>
	 * This beacon work as any <code>TickBeacon</code> object but specifically
	 * send a <code>RTEvent</code> in the <code>onTick</code> method in order
	 * to synchronize all objects.
	 * </p><p>
	 * The RTBeacon can be plugged on any other beacon, but by default
	 * the beacon operate on the swf framerate with the FPSBeacon.
	 * </p><p>
	 * The paradigm behind the RTBeacon is that all objects
	 * have to evolve in time not by frame but according to a real time unit,
	 * in that way objects animations can't be affected by framerate variations.
	 * The basic approach is to define all equations on a second-based unit.
	 * Then, even if the framerate change during time or according browser 
	 * restrictions, all objects will move at the same speed.
	 * </p><p>
	 * The RTBeacon object provide many methods to control <i>time scale</i> and
	 * <i>motion smoothness</i> directly from the source, when using those methods
	 * all objects are affected by the changes.
	 * <br/><ul>
	 * <li>Time scale is controlled by a global speed modificator which
	 * allow to multiply or divide the speed of animations. The dispatched
	 * timestepis allways modified by the global speed parameter.</li>
	 * <li>Motion smoothness control is done by two settings, the <i>maximum
	 * step size</i> and the <i>smooth</i> parameter. The first control
	 * the maximum value the RTBeacon dispatch with the <code>onTick</code>
	 * event, the second control the regularity of the step value by returning
	 * an average value of a limited number of frames. The number of frames
	 * used to calculate the average value is equals to the
	 * <code>getSmoothness</code> returns.</li>
	 * </ul>
	 * </p><p>
	 * The RTBeacon is a singleton object. Use the <code>getInstance</code>
	 * method to get a reference to it.
	 * </p>
	 * @author Cédric Néhémie
	 * @example  Building a simple class that move an object on X axis based on the timestep
	 * dispatched by the RTBeacon. Here moves are write in px/s.
	 * 
	 * <listing>import com.bourre.transitions.TickListener;

	 * class Mover extends RTObject implements TickListener
	 * {
	 * 	protected var _oTarget : MovieClip; // Object to move.
	 * 	protected var _nSpeed : Number; // Speed in px/s.
	 * 
	 * 	public function Mover ( o : MovieClip, speed : Number = 1 )
	 * 	{
	 * 		super( abstractRTObjectConstructorAccess );
	 * 
	 * 		_nSpeed = speed;
	 *		_oTarget = o;
	 * 			
	 * 		// start the animation
	 * 		start();
	 * 	} 
	 * 	public function onTick ( e : Event = null ) : void
	 * 	{
	 * 		// current speed computed with the RTEvent.getStepSecond() method
	 * 		var curSpeed : Number = _nSpeed * ( e as RTEvent ).getStepSecond() ); 
	 * 
	 * 		_oTarget.x += curSpeed;
	 * 	}
	 * }
	 * </listing>
	 */ 
	public class RTBeacon implements TickBeacon, TickListener
	{
		
		private static var _oI : RTBeacon;
		
		/**
		 * Returns a reference to the <code>RTBeacon</code> instance.
		 *	
		 * @return	The <code>RTBeacon</code> instance.
		 * @example	Getting the sole instance of RTBeacon
		 * <listing>var rtInstance : RTBeacon = RTBeacon.getInstance();</listing>
		 */	
		public static function getInstance () : RTBeacon
		{
			if ( _oI == null )
			{
				_oI = new RTBeacon ();
			}
			return _oI;
		}
		/**
		 * Destroy the <code>RTBeacon</code> instance.
		 * 
		 * <p>Warning <code>onTick</code> event will never be 
		 * broadcasted until the next <code>getInstance</code> call.</p>
		 */
		public static function release() : void
		{
			_oI.stop();
			_oI = null;
		}
		
		// Frame count
		protected  var _nC : Number;
		// Frame duration
		protected  var _nS : Number;
		// Frame duration in seconds
		protected  var _nSc : Number;
		// Last frame time
		protected  var _nMs : Number;
		// Max step size
		protected  var _nMSS : Number;
		// Global Speed
		protected  var _nGS : Number;
		// Is playing ? 
		protected  var _bIP : Boolean;
		//Original Beacon
		protected  var _oBeacon : TickBeacon; 
		// Smooth amount
		protected  var _nSmooth : Number;
		// Smooth array
		protected  var _aSmoothValues : Array;
		// Smooth value sum
		protected  var _nSmoothSum : Number;
		// Dispatching
		protected var _oED : EventDispatcher;
		
		public var lastProcessTime : Number;
		
		/**
		 * @protected
		 */
		public function RTBeacon ()
		{
			_oBeacon = FPSBeacon.getInstance();
			_nGS = 1;
			_nC = 0;
			_nS = 0;
			_nSc = 0;
			_nMs = getTimer();
			_nMSS = 0;
			_aSmoothValues = [];
			_nSmooth = 0;
			_nSmoothSum = 0;
			_oED = new EventDispatcher();
		}
		
		/**
		 * Proceed all times operation and broadcast a <code>RTEvent</code>.
		 */	
		public function onTick ( e : Event = null ) : void
		{
			var cT:Number = getTimer();
			
			_nS = ( cT - _nMs ) * _nGS;
			
			_onTick ();
			
			_nMs = cT;
		}
		
		/**
		 * Used internally to procede beacon behaviors and
		 * dispatch event. 
		 */
		protected function _onTick () : void
		{
			var ms : Number = getTimer();
			
			if( _nMSS ) _nS = _restrict( _nS );
			if( _nSmooth ) _nS = _smooth ( _nS );
			
			_nSc = _nS / 1000;
			
			var evt : RTEvent = new RTEvent ( Event.ENTER_FRAME );
			evt.setStep ( _nS );
			evt.setStepInSecond ( _nSc );
			_oED.dispatchEvent( evt );			
			_nC++;
			
			lastProcessTime = getTimer() - ms;
		}
		
		/**
		 * Defines a maximum value for the <code>getStep</code> return value.
		 * 
		 * <p>Use that parameter to limit the lag incidence. When the frame
		 * duration becomes too high animations will make a big gap between each
		 * refresh. It can really be a big issue in games where many collisions
		 * detection will failed if the object pass through an object during
		 * the move.</p>
		 * 
		 * <p>If the parameter isn't specified, the vaue is set to 0</p>
		 *	
		 * @param	n Maximum time step in milliseconds.
		 * @example	Restrict the timestep to 100
		 * <listing>RTBeacon.getInstance().setMaxStepSize( 100 );</listing>
		 */	
		public function setMaxStepSize ( n : Number ) : void
		{
			_nMSS = n > 0 ? n : 0;
		}
		
		/**
		 * Returns the current maximum timestep size in milliseconds.
		 * 
		 * <p>By default return 0.</p>
		 *	
		 * @return	<code>Number</code>
		 * @example	Getting the last defined timestep limit
		 * <listing>var maxStepSize : Number = RTBeacon.getInstance().getMaxStepSize();</listing>
		 */	
		public function getMaxStepSize () : Number
		{
			return _nMSS;
		}
		
		/**
		 * Defines a speed rate for the <code>RTBeacon</code>, it works as a
		 * multiplier for timesteps.
		 * 
		 * <p>By default the speed rate is set to 1, you can use this parameter
		 * to create time stretching effects.</p>
		 * 
		 * <p>You cannot specify values lower or equals than 0</p>
		 * 
		 * @param   n 	A new speed rate fot the <code>RTBeacon</code>.
		 * @example	Divide the speed by 2
		 * <listing>RTBeacon.getInstance().setGlobalSpeed ( .5 );</listing>
		 * Multiply the speed by 2
		 * <listing>RTBeacon.getInstance().setGlobalSpeed ( 2 );</listing>
		 */
		public function setGlobalSpeed ( n : Number ) : void
		{
			if( n <= 0 ) return;
	
			_nGS = n;
		}
		
		/**
		 * Returns the current speed rate of the <code>RTBeacon</code>.
		 * 
		 * @return	<code>Number</code> modificator for speed.
		 * @example	Returning the last defined global speed.
		 * <listing>var globalSpeed : Number = RTBeacon.getInstance().getGlobalSpeed ();</listing>
		 */
		public function getGlobalSpeed (): Number
		{
			return _nGS;
		}
		
		/**
		 * Defines the amount of smouthness of the <code>RTBeacon</code>
		 * step value.
		 * 
		 * <p>Set <i>smooth</i> on 0 to deactivate all smooth calculations.</p>
		 * 
		 * <p>Smoothness is basically the average of a limited number
		 * of tilestep duration. The more values there is in the average
		 * calculation the less the resulting timestep may vary from a frame
		 * to the other.
		 * <br/>
		 * The smooth parameter could create really awful side-effects.
		 * If you define smooth without setting a maximum step size, a big
		 * lag may occurs the animation to speed up after the lag, and in that
		 * case, the more high the smooth is the more the speed up effect will
		 * run for a long time. Then when using smooth try to define a maximum
		 * step size about two times the frame duration.</p>
		 * 
		 * @param	smooth	The amount of smoothness in step values.
		 * @example	Create a smooth on the 10 last values.
		 * <listing>RTBeacon.getInstance().setSmoothness ( 10 );</listing>
		 */
		public function setSmoothness ( smooth : Number ) : void
		{
			if( !smooth || smooth < 0 ) smooth = 0;
			
			_nSmooth = smooth;
			
			var l : Number = _aSmoothValues.length;
			if( l > _nSmooth )
			{
				l = l - _nSmooth;
				while ( --l -(-1 ) )
					_nSmoothSum -= _aSmoothValues.shift() as Number;
			}
		}
		
		/**
		 * Returns the current smoothness amount.
		 * 
		 * @return	<code>Number</code> amount of smooth.
		 * @example	<listing>var smooth : Number = RTBeacon.getInstance().getSmoothness ();</listing>
		 */
		public function getSmoothness () : Number
		{
			return _nSmooth;
		}
		
		/**
		 * Used to perform the smooth
		 */
		protected function _smooth ( n : Number) : Number
		{
			_aSmoothValues.push( n );
			var l : Number = _aSmoothValues.length;
			if( l > _nSmooth )
			{
				_nSmoothSum -= Number( _aSmoothValues.shift() );
				l--;
			}
			_nSmoothSum += n;
			
			return _nSmoothSum / l;
		} 
		
		/**
		 * Used to perform the step restriction
		 */
		protected function _restrict ( n : Number ) : Number
		{
			var l : Number = _nMSS * _nGS;
			 return n > l ? l : n;
		}
		
		/**
		 * Returns the last frame duration in milliseconds.
		 * 
		 * @return	<code>Number</code> of milliseconds since
		 * 			the last <code>onTick</code> call.
		 * @example	<listing>var seconds : Number = RTBeacon.getInstance().getStep ();</listing>
		 */	
		public function getStep () : Number
		{
			return _nS;
		}
		
		/**
		 * Returns the last frame duration in seconds.
		 *
		 * @return	<code>Number</code> of seconds since
		 * 			the last <code>onTick</code> call.
		 * @example	<listing>var seconds : Number = RTBeacon.getInstance().getStepInSecond ();</listing>
		 */	
		public function getStepInSecond () : Number
		{
			return _nSc;
		}
		
		/**
		 * Returns the number of frames the <code>RTBeacon</code> run.
		 *	
		 * @return <code>Number</code> of frames since the 
		 * 		   <code>RTBeacon</code> start.
		 * @example	<listing>var count : Number = RTBeacon.getInstance().getCount ();</listing>
		 */	
		public function getCount () : Number
		{
			return _nC;
		}
		
		/**
		 * Starts the process.
		 * 
		 * @example <listing>RTBeacon.getInstance().start();</listing>
		 */
		public function start() : void
		{
			_nMs = getTimer();
			_bIP = true;
			_oBeacon.addTickListener( this );
		}
		
		/**
		 * Stops the process.
		 * 
		 * @example <listing>RTBeacon.getInstance().stop();</listing>
		 */
		public function stop() : void
		{
			_oBeacon.removeTickListener( this );
			_bIP = false;
		}
		
		/**
		 * Indicates if RTBeacon's running.
		 * 
		 * @return <code>true</code> if <code>RTBeacon</code> is running, either <code>false</code>
		 * @example <listing>var b:Boolean = RTBeacon.getInstance().isPlaying();</listing>
		 */
		public function isPlaying() : Boolean
		{
			return _bIP;
		}
		
		/**
		 * Add a listener to this object.
		 * 
		 * <p>The listener must implements <code>TickListener</code>
		 * and provide an <code>onTick</code> method.</p>
		 * 
		 * <p>Listeners are systematically registered as weak references.</p>
		 * @param	listener	The object to add as listener for this object.
		 */
		public function addTickListener( listener : TickListener ) : void
		{
			if( !hasTickListener () ) start();
			
			_oED.addEventListener( Event.ENTER_FRAME, listener.onTick, false, 0, true );
		}
		
		/**
		 * Remove a listener from receiving the <code>onTick</code> event.
		 * 
		 * @param listener The object to remove as listener for this object.
		 */
		public function removeTickListener( listener : TickListener ) : void
		{
			_oED.removeEventListener( Event.ENTER_FRAME, listener.onTick );
			
			if( !hasTickListener () ) stop();
		}
		
		/**
		 * Is there any listeners for the <code>onTick</code> event ?
		 * 
		 * @return <code>true</code> if the object have listeners to 
		 * 		   the <code>onTick</code> event.
		 */
		public function hasTickListener () : Boolean
		{
			return _oED.hasEventListener( Event.ENTER_FRAME );
		}
		
		/**
		 * Define on which beacon the <code>RTBeacon</code> run.
		 * 
		 * <p>By default the <code>RTBeacon</code> is plugged on
		 * the <code>FPSBeacon</code> of Pixlib, but you can swap to any
		 * other beacon at any time.</p>
		 * 
		 * <p>The time computation in the beacon isn't affected by the
		 * way the composed beacon work. So don't use an <code>FLVBeacon</code>
		 * to synchronize a tweens with a streamed video within Kairos, just
		 * use what Pixlib already provide.</p>
		 * 
		 * @param	b	<code>TickBeacon</code>
		 * @example	RTBeacon.getInstance().setTickBeacon ( MSBeacon.getInstance() );
		 */
		public function setTickBeacon ( b : TickBeacon ) : void
		{
			if( b )
			{
				if( _bIP )
				{
					stop();
					if( b ) _oBeacon = b;
					start();
				}
				else _oBeacon = b;
			}
			
		}
		
		/**
		 * Return the String representation of the object.
		 */	
		public function toString() : String
		{
			return PixlibStringifier.stringify( this );
		}
	}
}